/*
 * Copyright (c) 2008-2009, Computational Crawling LP
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
 *
 *    * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
 *    * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
 *    * Neither the name of Computational Crawling LP, 80legs, nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package com.eightylegs.customer.processtest;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.Calendar;

import com.pluraprocessing.node.classloader.PluraClassLoader;


/**
 * Class CrawlProcessTest - this is the main class for the project and implements local testing of the customer's WebAnalysis code
 * 
 */
public class CrawlProcessTest {
	
	/**
	 * This is the main entry point for the project.  
	 *    
	 * @param args - takes multiple user parameters that determine how to test the user's 80legs WebAnalysis code:
	 * 
	 * 	  args[0] = <80legsProcessTest-jar-fileName> - 80legsProcessTes jar (provided by 80legs)
	 * 	  args[1] = <test-jar-fileName> - jar to be tested - must contain a WebAnalysis class
	 *    args[2] = <optional-doc-info-fileName> - Contains the extra info to be passed into the WebAnalysis.processDocument() function.  Format of the doc-info-fileName file:
	 *			URL=<url-name> - url of page as a string to be passed into processDocument()
	 *			STATUS=<status-string> - status as a string to be passed into processDocument()
	 *			HEADER=<key>,<value> - pairs of strings separated by a comma - this line should repeat for each header you want to simulate
	 *    args[3] = <optional-doc-contents-fileName> - The contents of the document you want to process via WebAnalysis.processDocument()
	 *    args[4] = <optional-data-fileName> - this is the data that will be passed into WebAnalysis.deserializeData()
	 */
	public static void main(String[] args) {
		if (( args.length < 1 ) || ( args.length > 4 )) {
			//System.out.println("Usage: java -Xmx256m -jar <80legsProcessTest-jar-fileName> <80legs-process-test-jar> <test-jar-fileName> <optional-doc-info-fileName> <optional-doc-contents-fileName> <optional-data-fileName>");
			System.out.println("Usage: java -Xmx256m -jar <80legs-process-test-jar> <test-jar-fileName> <optional-doc-info-fileName> <optional-doc-contents-fileName> <optional-data-fileName>");
			System.out.println("If the <optional-doc-info-fileName> or <optional-doc-contents-fileName> are not specified, dummy values are provided.");
			System.out.println("If the <optional-data-fileName> is not provided, the data passed to your initialize() function will be null");
			System.out.println("Format of the doc-info-fileName file:");
			System.out.println("  URL=<url-name> (url of page as a string to be passed into processDocument())");
			System.out.println("  STATUS=<status-string> (status as a string to be passed into processDocument())");
			System.out.println("  HEADER=<key>,<value> (pairs of strings separated by a comma - this line should repeat for each header you want to simulate)");
			System.exit(1);
		}

		try {
			//String eightyJarFileName = args[0];
			String jarFileName = args[0];
			String docInfoFileName = null;
			if ( args.length >= 2 ) {
				docInfoFileName = args[1];
			}
			String docContentsFileName = null;
			if ( args.length >= 3 ) {
				docContentsFileName = args[2];
			}
			String dataFileName = null;
			if ( args.length == 4 ) {
				dataFileName = args[3];
			}

			// convert to absolute path names
			jarFileName = new File(jarFileName).getAbsolutePath();
			//eightyJarFileName = new File(eightyJarFileName).getAbsolutePath();
			if ( docInfoFileName != null ) {
				docInfoFileName = new File(docInfoFileName).getAbsolutePath();
			}
			if ( docContentsFileName != null ) {
				docContentsFileName = new File(docContentsFileName).getAbsolutePath();
			}
			if ( dataFileName != null ) {
				dataFileName = new File(dataFileName).getAbsolutePath();
			}

			// run the test
			URL[] urls = new URL[2];
			Boolean[] load = new Boolean[2];
			urls[0] = new URL("file:///" + jarFileName); 
			load[0] = true;
			urls[1] = new CrawlProcessTest().getClass().getResource("/jars/80legsProcessTest.jar").toURI().toURL(); 	
			load[1] = true;
			PluraClassLoader loader = new PluraClassLoader(urls, load);
			
			
			//read data/info files
			SimulatedPageInfo pageInfo = readPageInfo(docInfoFileName);
			byte[] pageContent = readPageContents(docContentsFileName);
			byte[] dataContent = loadDataFile(dataFileName);
			
			Class<?>[] constructorArgTypes = new Class<?>[1];
			constructorArgTypes[0] = byte[].class;
			
			Object[] constructorArgs = new Object[1];
			constructorArgs[0] = dataContent;
			
			Object[] methodArgs = new Object[2];
			methodArgs[0] = pageInfo;
			methodArgs[1] = pageContent;
			
			Class<?>[] methodArgTypes = new Class<?>[2];
			methodArgTypes[0] = SimulatedPageInfo.class;
			methodArgTypes[1] = byte[].class;
			
			Object obj = loader.loadClass("com.eightylegs.customer.processtest.Test80legsUserCode").getConstructor(constructorArgTypes).newInstance(constructorArgs);

			obj.getClass().getMethod("testPage", methodArgTypes).invoke(obj, methodArgs);		
			
		}
		catch ( Throwable e ) { 
			System.out.println( now() + " Unrecoverable error: ");
			e.printStackTrace();			
		}
	}
	
	/**
	 * read's the extra page information that will be passed into the processDocument() function  
	 *    
	 * @param fileName - this file contains the information about the page to be tested (url, headers, and statusCode)
	 * @return a SimulatedPageInfo object containing the extra information that will be passed into processDocument()
	 */
	private static SimulatedPageInfo readPageInfo ( String fileName ) throws Exception {
	    SimulatedPageInfo info = new SimulatedPageInfo();
	    
	    if ( fileName == null ) {
	    	info.setUrl("http://www.80legs.com");
    		info.setStatusCodeLine ( "200" );
    		info.addHeader ( "DUMMY", "VALUE" );
	    }
	    else {
			BufferedReader input =  new BufferedReader(new FileReader(fileName));
			String line = null;
		    while (( line = input.readLine()) != null) {
		    	if ( line.startsWith("URL=") == true ) {
		    		info.setUrl( line.substring(line.indexOf("=")+1) );
		    	}
		    	if ( line.startsWith("STATUS=") == true ) {
		    		info.setStatusCodeLine ( line.substring(line.indexOf("=")+1) );
		    	}
		    	if ( line.startsWith("HEADER=") == true ) {
		    		String fullHeaderString = line.substring(line.indexOf("=")+1);
		    		String[] headerSplits = fullHeaderString.split(",",2);
		    		info.addHeader ( headerSplits[0], headerSplits[1] );
		    	}
		    }
		    if ( info.getUrl() == null ) {
		    	throw ( new Exception ( "URL= does not exist in " + fileName ) );
		    }
		    if ( info.getHeaders().size() == 0 ) {
		    	throw ( new Exception ( "HEADER= does not exist in " + fileName ) );
		    }
		    if ( info.getStatusCodeLine() == null ) {
		    	throw ( new Exception ( "STATUS= does not exist in " + fileName ) );
		    }
	    }
	    
	    return info;
	}

	/**
	 * reads the contents of the input file and returns them as a byte[]  
	 *    
	 * @param fileName - this file contains the user's data to be passed into WebAnalysis.deserializeData()
	 * @return a byte[] containing the contents of the file
	 */
	private static byte[] readPageContents ( String fileName ) throws IOException {
		byte[] docContents;
		
		if ( fileName == null ) {
			String docContentsString = "This is just the default file contents.  To get real contents, use the arguments to the jar.";
			docContents = docContentsString.getBytes("UTF-8");
		}
		else {
			int docContentsSize = (int)new File(fileName).length();
			docContents = new byte[docContentsSize];
			BufferedInputStream r = new BufferedInputStream(new FileInputStream(fileName));
			r.read(docContents);
			r.close();
		}
		
		return docContents;
	}
	
	private static byte[] loadDataFile(String dataFileName) throws Exception {
		byte[] userData = null;
		
		try {
			// read all the binary contents of dataFileName
			if ( dataFileName != null ) {
				int fileSize = (int)new File(dataFileName).length();
		    	userData = new byte[fileSize];
				BufferedInputStream r = new BufferedInputStream(new FileInputStream(dataFileName));
				r.read(userData);
				r.close();
			}
		}
		catch ( Exception e ) {
			System.out.println( now() + " error reading user data file " + dataFileName + "\n" );  
			throw ( e );
		}
		
		return userData;
	}
	
	/**
	 * returns a String with the formatted current time
	 *    
	 * @return formatted current time
	 */
	private static String now() {
		final String DATE_FORMAT_NOW = "yyyy-MM-dd HH:mm:ss:SSS";
	    Calendar cal = Calendar.getInstance();
	    SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT_NOW);
	    return sdf.format(cal.getTime());
	}
}
